<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
 "http://www.w3.org/TR/2002/REC-xhtml1-20020801/DTD/xhtml1-strict.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
  <meta http-equiv="Content-Type"
        content="text/html; charset=ISO-8859-1" />
  <title>Code Examples for Programming in Scala</title>
  <link rel="stylesheet" href="style.css" type="text/css"/>
</head>
<body>

<div id="mainTitles"><h3>Code Examples for</h3><h2>Programming in Scala</h2></div>  <p><a href="../index.html">
    Return to chapter index
  </a></p>
  <h2>33 The <span class="mono">SCells</span> Spreadsheet</h2>

    <li>33.1 <a href="#sec1">The visual framework</a></li>
    <li>33.2 <a href="#sec2">Disconnecting data entry and display</a></li>
    <li>33.3 <a href="#sec3">Formulas</a></li>
    <li>33.4 <a href="#sec4">Parsing formulas</a></li>
    <li>33.5 <a href="#sec5">Evaluation</a></li>
    <li>33.6 <a href="#sec6">Operation libraries</a></li>
    <li>33.7 <a href="#sec7">Change propagation</a></li>
    <li>33.8 <a href="#sec8">Conclusion</a></li>
  </ul>

  <h3><a name="sec1"></a>33.1 The visual framework</h3>

  <pre><hr>
// In file <a href="../scells/ex1/Spreadsheet.scala">scells/ex1/Spreadsheet.scala</a>

  package org.stairwaybook.scells
  import swing._

  class Spreadsheet(val height: Int, val width: Int) 
      extends ScrollPane {

    val table = new Table(height, width) {
      rowHeight = 25
      autoResizeMode = Table.AutoResizeMode.Off
      showGrid = true
      gridColor = new java.awt.Color(150, 150, 150)
    }

    val rowHeader = 
      new ListView((0 until height) map (_.toString)) {
        fixedCellWidth = 30
        fixedCellHeight = table.rowHeight
      }

    viewportView = table
    rowHeaderView = rowHeader
  }

<hr>
// In file <a href="../scells/ex5/Main.scala">scells/ex5/Main.scala</a>

  package org.stairwaybook.scells
  import swing._

  object Main extends SimpleGUIApplication {
    def top = new MainFrame {
      title = "ScalaSheet"
      contents = new Spreadsheet(100, 26)
    }
  }

<hr>
  </pre>
  <h3><a name="sec2"></a>33.2 Disconnecting data entry and display</h3>

  <pre><hr>
// In file <a href="../scells/ex2/Spreadsheet.scala">scells/ex2/Spreadsheet.scala</a>

  package org.stairwaybook.scells
  import swing._

  class Spreadsheet(val height: Int, val width: Int) 
      extends ScrollPane {

    val cellModel = new Model(height, width)
    import cellModel._
    
    val table = new Table(height, width) {

      // settings as before... 

      override def rendererComponent(isSelected: Boolean,
          hasFocus: Boolean, row: Int, column: Int): Component =

        if (hasFocus) new TextField(userData(row, column))
        else
          new Label(cells(row)(column).toString) { 
            xAlignment = Alignment.Right 
          }

      def userData(row: Int, column: Int): String = {
        val v = this(row, column)
        if (v == null) "" else v.toString
      }
    }
    // rest as before... 
  } 

<hr>
// In file <a href="../scells/ex3/Model.scala">scells/ex3/Model.scala</a>

  package org.stairwaybook.scells

  class Model(val height: Int, val width: Int) { 

    case class Cell(row: Int, column: Int)

    val cells = new Array[Array[Cell]](height, width) 

    for (i &lt;- 0 until height; j &lt;- 0 until width) 
      cells(i)(j) = new Cell(i, j)
  }

<hr>
  </pre>
  <h3><a name="sec3"></a>33.3 Formulas</h3>

  <pre><hr>
// In file <a href="../scells/ex5/Formulas.scala">scells/ex5/Formulas.scala</a>

  package org.stairwaybook.scells

  trait Formula  

  case class Coord(row: Int, column: Int) extends Formula {
    override def toString = ('A' + column).toChar.toString + row
  }
  case class Range(c1: Coord, c2: Coord) extends Formula {
    override def toString = c1.toString +":"+ c2.toString
  }
  case class Number(value: Double) extends Formula {
    override def toString = value.toString 
  }
  case class Textual(value: String) extends Formula { 
    override def toString = value
  }
  case class Application(function: String, 
      arguments: List[Formula]) extends Formula { 

    override def toString = 
      function + arguments.mkString("(", ",", ")")
  }
  object Empty extends Textual("")

<hr>
  </pre>
  <h3><a name="sec4"></a>33.4 Parsing formulas</h3>

  <pre><hr>
// In file <a href="../scells/ex5/FormulaParsers.scala">scells/ex5/FormulaParsers.scala</a>

  package org.stairwaybook.scells
  import scala.util.parsing.combinator._

  object FormulaParsers extends RegexParsers { 

<hr>
// In file <a href="../scells/ex5/FormulaParsers.scala">scells/ex5/FormulaParsers.scala</a>

    def ident: Parser[String] = """[a-zA-Z_]\w*""".r
    def decimal: Parser[String] = """-?\d+(\.\d*)?""".r

<hr>
// In file <a href="../scells/ex5/FormulaParsers.scala">scells/ex5/FormulaParsers.scala</a>

    def cell: Parser[Coord] = 
      """[A-Za-z]\d+""".r ^^ { s =&gt; 
        val column = s.charAt(0) - 'A'
        val row = s.substring(1).toInt
        Coord(row, column)
      }

<hr>
// In file <a href="../scells/ex5/FormulaParsers.scala">scells/ex5/FormulaParsers.scala</a>

    def range: Parser[Range] = 
      cell~":"~cell ^^ { 
        case c1~":"~c2 =&gt; Range(c1, c2) 
      }

<hr>
// In file <a href="../scells/ex5/FormulaParsers.scala">scells/ex5/FormulaParsers.scala</a>

  def number: Parser[Number] =
    decimal ^^ (d =&gt; Number(d.toDouble))

<hr>
// In file <a href="../scells/ex5/FormulaParsers.scala">scells/ex5/FormulaParsers.scala</a>

    def application: Parser[Application] = 
      ident~"("~repsep(expr, ",")~")" ^^ { 
        case f~"("~ps~")" =&gt; Application(f, ps) 
      }

<hr>
// In file <a href="../scells/ex5/FormulaParsers.scala">scells/ex5/FormulaParsers.scala</a>

    def expr: Parser[Formula] =
      range | cell | number | application

<hr>
// In file <a href="../scells/ex5/FormulaParsers.scala">scells/ex5/FormulaParsers.scala</a>

    def textual: Parser[Textual] = 
      """[^=].*""".r ^^ Textual

<hr>
// In file <a href="../scells/ex5/FormulaParsers.scala">scells/ex5/FormulaParsers.scala</a>

    def formula: Parser[Formula] = 
      number | textual | "="~&gt;expr

<hr>
// In file <a href="../scells/ex5/FormulaParsers.scala">scells/ex5/FormulaParsers.scala</a>

    def parse(input: String): Formula =
      parseAll(formula, input) match {
        case Success(e, _) =&gt; e
        case f: NoSuccess =&gt; Textual("["+ f.msg +"]")
      }
  } //end FormulaParsers

<hr>
// In file <a href="../scells/ex4/Spreadsheet.scala">scells/ex4/Spreadsheet.scala</a>

  package org.stairwaybook.scells
  import swing._
  import event._

  class Spreadsheet(val height: Int, val width: Int) ... {
    val table = new Table(height, width) {
      ...
      reactions += {
        case TableUpdated(table, rows, column) =&gt;
          for (row &lt;- rows)
            cells(row)(column).formula = 
              FormulaParsers.parse(userData(row, column))
      }
    }
  }

<hr>
// In file <a href="../scells/ex3/Model.scala">scells/ex3/Model.scala</a>

    case class Cell(row: Int, column: Int) {
      var formula: Formula = Empty
      override def toString = formula.toString
    }

<hr>
  TableUpdated(table, rows, column)

<hr>
  </pre>
  <h3><a name="sec5"></a>33.5 Evaluation</h3>

  <pre><hr>
// In file <a href="../scells/ex5/Evaluator.scala">scells/ex5/Evaluator.scala</a>

  package org.stairwaybook.scells
  trait Evaluator { this: Model =&gt; ...

<hr>
// In file <a href="../scells/ex5/Evaluator.scala">scells/ex5/Evaluator.scala</a>

    def evaluate(e: Formula): Double = try {
      e match {
        case Coord(row, column) =&gt; 
          cells(row)(column).value
        case Number(v) =&gt; 
          v
        case Textual(_) =&gt; 
          0
        case Application(function, arguments) =&gt;
          val argvals = arguments flatMap evalList
          operations(function)(argvals)
      }
    } catch {
      case ex: Exception =&gt; Math.NaN_DOUBLE
    }

<hr>
// In file <a href="../scells/ex5/Evaluator.scala">scells/ex5/Evaluator.scala</a>

    type Op = List[Double] =&gt; Double
    val operations = new collection.mutable.HashMap[String, Op]

<hr>
// In file <a href="../scells/ex5/Evaluator.scala">scells/ex5/Evaluator.scala</a>

    private def evalList(e: Formula): List[Double] = e match {
      case Range(_, _) =&gt; references(e) map (_.value)
      case _ =&gt; List(evaluate(e))
    }

<hr>
// In file <a href="../scells/ex5/Evaluator.scala">scells/ex5/Evaluator.scala</a>

    def references(e: Formula): List[Cell] = e match {
      case Coord(row, column) =&gt; 
        List(cells(row)(column))
      case Range(Coord(r1, c1), Coord(r2, c2)) =&gt;
        for (row &lt;- (r1 to r2).toList; column &lt;- c1 to c2)
        yield cells(row)(column)
      case Application(function, arguments) =&gt;
        arguments flatMap references
      case _ =&gt;
        List()
    }
  } // end Evaluator

<hr>
  </pre>
  <h3><a name="sec6"></a>33.6 Operation libraries</h3>

  <pre><hr>
// In file <a href="../scells/ex5/Arithmetic.scala">scells/ex5/Arithmetic.scala</a>

  package org.stairwaybook.scells
  trait Arithmetic { this: Evaluator =&gt;
    operations += (
      "add"  -&gt; { case List(x, y) =&gt; x + y },
      "sub"  -&gt; { case List(x, y) =&gt; x - y },
      "div"  -&gt; { case List(x, y) =&gt; x / y },
      "mul"  -&gt; { case List(x, y) =&gt; x * y },
      "mod"  -&gt; { case List(x, y) =&gt; x % y },
      "sum"  -&gt; { xs =&gt; (0.0 /: xs)(_ + _) },
      "prod" -&gt; { xs =&gt; (1.0 /: xs)(_ * _) }
    )
  }

<hr>
  { case List(x, y) =&gt; x + y }

<hr>
// In file <a href="../scells/ex4/Model.scala">scells/ex4/Model.scala</a>

  package org.stairwaybook.scells

  class Model(val height: Int, val width: Int) 
      extends Evaluator with Arithmetic { 

    case class Cell(row: Int, column: Int) {
      var formula: Formula = Empty
      def value = evaluate(formula)

      override def toString = formula match {
        case Textual(s) =&gt; s
        case _ =&gt; value.toString
      }
    }
    
    ... // rest as before
  }

<hr>
  </pre>
  <h3><a name="sec7"></a>33.7 Change propagation</h3>

  <pre><hr>
// In file <a href="../scells/ex5/Model.scala">scells/ex5/Model.scala</a>

  package org.stairwaybook.scells
  import swing._

  class Model(val height: Int, val width: Int) 
  extends Evaluator with Arithmetic { 

<hr>
// In file <a href="../scells/ex5/Model.scala">scells/ex5/Model.scala</a>

    case class Cell(row: Int, column: Int) extends Publisher {
      override def toString = formula match {
        case Textual(s) =&gt; s
        case _ =&gt; value.toString
      }

<hr>
// In file <a href="../scells/ex5/Model.scala">scells/ex5/Model.scala</a>

      private var v: Double = 0
      def value: Double = v
      def value_=(w: Double) {
        if (!(v == w || v.isNaN &amp;&amp; w.isNaN)) {
          v = w
          publish(ValueChanged(this))
        }
      }

<hr>
// In file <a href="../scells/ex5/Model.scala">scells/ex5/Model.scala</a>

      private var f: Formula = Empty
      def formula: Formula = f
      def formula_=(f: Formula) {
        for (c &lt;- references(formula)) deafTo(c)
        this.f = f
        for (c &lt;- references(formula)) listenTo(c)
        value = evaluate(f)
      }

<hr>
// In file <a href="../scells/ex5/Model.scala">scells/ex5/Model.scala</a>

      reactions += {
        case ValueChanged(_) =&gt; value = evaluate(formula)
      }
    } // end class Cell

<hr>
// In file <a href="../scells/ex5/Model.scala">scells/ex5/Model.scala</a>

    case class ValueChanged(cell: Cell) extends event.Event

<hr>
// In file <a href="../scells/ex5/Model.scala">scells/ex5/Model.scala</a>

    val cells = new Array[Array[Cell]](height, width) 

    for (i &lt;- 0 until height; j &lt;- 0 until width)
      cells(i)(j) = new Cell(i, j)
  } // end class Model

<hr>
// In file <a href="../scells/ex5/Spreadsheet.scala">scells/ex5/Spreadsheet.scala</a>

  package org.stairwaybook.scells
  import swing._, event._

  class Spreadsheet(val height: Int, val width: Int) 
      extends ScrollPane {

    val cellModel = new Model(height, width)
    import cellModel._

    val table = new Table(height, width) {
      ... // settings as in Listing X

      override def rendererComponent(
           isSelected: Boolean, hasFocus: Boolean, 
           row: Int, column: Int) =
        ... // as in Listing X

      def userData(row: Int, column: Int): String = 
        ... // as in Listing X

      reactions += {
        case TableUpdated(table, rows, column) =&gt;
          for (row &lt;- rows)
            cells(row)(column).formula = 
              FormulaParsers.parse(userData(row, column))
        case ValueChanged(cell) =&gt;
          updateCell(cell.row, cell.column)
      }

      for (row &lt;- cells; cell &lt;- row) listenTo(cell)
    }

    val rowHeader = new ListView(0 until height) {
      fixedCellWidth = 30
      fixedCellHeight = table.rowHeight
    }

    viewportView = table
    rowHeaderView = rowHeader
  }

<hr>
  </pre>
  <h3><a name="sec8"></a>33.8 Conclusion</h3>


 <table>
 <tr valign="top">
 <td>
 <div id="moreinfo">
 <p>
 For more information about <em>Programming in Scala</em> (the "Stairway Book"), please visit:
 </p>
 
 <p>
 <a href="http://www.artima.com/shop/programming_in_scala">http://www.artima.com/shop/programming_in_scala</a>
 </p>
 
 <p>
 and:
 </p>
 
 <p>
 <a href="http://booksites.artima.com/programming_in_scala">http://booksites.artima.com/programming_in_scala</a>
 </p>
 </div>
 </td>
 <td>
 <div id="license">
 <p>
   Copyright &copy; 2007-2008 Artima, Inc. All rights reserved.
 </p>

 <p>
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
 </p>

 <p style="margin-left: 20px">
   <a href="http://www.apache.org/licenses/LICENSE-2.0">
     http://www.apache.org/licenses/LICENSE-2.0
   </a>
 </p>

 <p>
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
   implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 </p>
 </div>
 </td>
 </tr>
 </table>

</body>
</html>
